package com.tengju.bff.interfaces.covercharge.util;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 *@author liang_shi
 *@date 2020/7/13 15:17
 *@description 
 */
public class HttpClientManager {

    private static Logger LOG = LoggerFactory.getLogger(HttpClientManager.class);

    private static volatile CloseableHttpClient httpClient = null;

    private static final Object SYNCLOCK = new Object();

    private HttpClientManager() {
    }


    public static CloseableHttpClient getHttpClient(String tlsv) {
        if (null != httpClient) {
            return httpClient;
        }
        synchronized (SYNCLOCK) {
            if (null == httpClient) {

                //----------------------v2.0-------------------------//
                /*// 注册访问协议相关的Socket工厂  */
                SSLConnectionSocketFactory sslsf = null;
                try {
                    sslsf = new SSLConnectionSocketFactory(MySSLSocketFactory.getSSLContext(tlsv), MySSLSocketFactory.getDefaultHostnameVerifier());
                } catch (Exception e) {
                    LOG.error("SSLContext init error,", e);
                }

                final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                        //.register("https", SSLConnectionSocketFactory.getSystemSocketFactory())
                        .register("https", sslsf)
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .build();

                final PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
                /**/
                //----------------------v2.0-------------------------//


                manager.setMaxTotal(1200);
                // 设置整个连接池的最大连接数
                // 每个路由的默认最大连接，每个路由实际最大连接数默认为 DefaultMaxPerRoute 控制,而MaxTotal是控制整个池子的最大数
                // 设置过小无法支持大并发，(ConnectionPoolTimeoutException:Timeout waiting for connection from pool),路由是对maxTotal的细分
                manager.setDefaultMaxPerRoute(600);
                // 每个路由的最大连接数
                // 再从连接池获取连接时，连接不活跃多长时间后需要进行一次验证，默认为5s
                manager.setValidateAfterInactivity(5 * 1000);

                // 默认请求设置
                final RequestConfig defaultRequestConfig = RequestConfig.custom()
                        .setConnectionRequestTimeout(1000 * 3)
                        // 设置从连接池获取连接的等待超时时间 从连接池获取一个可用连接的等待超时：3s
                        .setConnectTimeout(1000 * 5)
                        // 设置连接超时时间 建立socket连接的超时(三次握手阶段)：5s
                        .setSocketTimeout(1000 * 5)
                        // 设置等待数据超时时间 socket读写超时(通信阶段)：30S
                        .setCookieSpec(CookieSpecs.STANDARD_STRICT).build();

                // 创建HttpClient
                httpClient = HttpClients.custom()
                        .setConnectionManager(manager)
                        //指定HttpClient使用的连接池
                        .setConnectionManagerShared(false)
                        // 连接池不是共享模式
                        .evictIdleConnections(60L, TimeUnit.SECONDS)
                        //定期回收空闲连接
                        .evictExpiredConnections()
                        // 定期回收过期连接
                        .setConnectionTimeToLive(60L, TimeUnit.SECONDS)
                        // 连接存活时间，如果不设置，则根据长连接信息决定
                        .setDefaultRequestConfig(defaultRequestConfig)
                        // 设置默认请求配置
                        .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                        // 连接重用策略，即是否能keepAlive
                        .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
                        // 长连接配置，即获取长连接生产多长时间
                        //.setRetryHandler(new HttpRequestRetryHandlerImpl(1)) //设置重试机制
                        .setRetryHandler(new DefaultHttpRequestRetryHandler(1, true))
                        // 设置重置次数，默认是3次；当前是禁用掉（根据需要开启）
                        .build();

                // JVM 停止或重启时，关闭连接池释放掉连接（跟数据库连接池类似）
                Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                    try {
                        LOG.debug("Runtime Shutdown，close httpClient");
                        httpClient.close();
                    } catch (final IOException e) {
                        LOG.error("addShutdownHook close error", e);
                    }
                }));

            }
        }
        return httpClient;
    }
    /**
     * Get http client closeable http client.
     *
     * @return the closeable http client
     */
    public static CloseableHttpClient getHttpClient() {
        return getHttpClient("TLSv1.1");
    }


    /**
     * 重试机制设置.
     */
    static class HttpRequestRetryHandlerImpl implements HttpRequestRetryHandler {

        int retryCount = 3;

        public HttpRequestRetryHandlerImpl() {
        }

        public HttpRequestRetryHandlerImpl(int retryCount) {
            this.retryCount = retryCount;
        }

        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (executionCount > retryCount) {
                // Do not retry if over max retry count
                return false;
            }

            if (exception instanceof org.apache.http.conn.ConnectTimeoutException) {
                LOG.info("org.apache.http.conn.ConnectTimeoutException ,so  retryRequest");
                return true;
            }

            final HttpClientContext clientContext = HttpClientContext.adapt(context);
            final HttpRequest request = clientContext.getRequest();

            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // Retry if the request is considered idempotent
                LOG.info("Retry if the request is considered idempotent ,so  retryRequest");
                return true;
            }

            if (!clientContext.isRequestSent()) {
                // Retry if the request has not been sent fully or
                // if it's OK to retry methods that have been sent
                LOG.info("Retry if the request has not been sent fully ,so  retryRequest");
                return true;
            }

            return false;
        }
    }


}
